#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
定时任务调度器
使用APScheduler来调度定时任务
"""

import os
import sys
import logging
from datetime import datetime, timedelta
from dotenv import load_dotenv
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ThreadPoolExecutor

# 添加项目路径到系统路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# 加载环境变量
load_dotenv()

from models import db, ScheduledTask
from utils import send_weekly_report_email
from app import create_app

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class TaskScheduler:
    def __init__(self):
        self.app = create_app()
        self.scheduler = None
        
    def start(self):
        """启动调度器"""
        # 创建调度器
        executors = {
            'default': ThreadPoolExecutor(20),
        }
        job_defaults = {
            'coalesce': False,
            'max_instances': 3
        }
        
        self.scheduler = BackgroundScheduler(executors=executors, job_defaults=job_defaults)
        
        # 添加定时任务检查作业（每分钟检查一次）
        self.scheduler.add_job(
            self.check_and_execute_tasks,
            'interval',
            minutes=1,
            id='check_tasks',
            name='检查并执行定时任务'
        )
        
        # 启动调度器
        self.scheduler.start()
        logger.info("定时任务调度器已启动")
        
    def stop(self):
        """停止调度器"""
        if self.scheduler:
            self.scheduler.shutdown()
            logger.info("定时任务调度器已停止")
            
    def check_and_execute_tasks(self):
        """检查并执行需要执行的定时任务"""
        logger.info("开始检查定时任务...")
        
        with self.app.app_context():
            # 获取所有启用的定时任务
            tasks = ScheduledTask.query.filter_by(is_active=True).all()
            logger.info(f"找到 {len(tasks)} 个启用的定时任务")
            
            now = datetime.now()
            executed_count = 0
            
            for task in tasks:
                try:
                    # 检查是否应该执行此任务
                    if self.should_execute_task(task, now):
                        logger.info(f"执行定时任务 ID: {task.id}, 用户ID: {task.user_id}")
                        
                        # 根据任务频率执行相应操作
                        if self.execute_task(task, now):
                            # 更新上次执行时间
                            task.last_run = now
                            db.session.commit()
                            executed_count += 1
                            logger.info(f"定时任务 ID: {task.id} 执行成功")
                        else:
                            logger.error(f"定时任务 ID: {task.id} 执行失败")
                except Exception as e:
                    logger.error(f"执行定时任务 ID: {task.id} 时出错: {str(e)}")
                    # 继续执行其他任务
            
            logger.info(f"定时任务检查完成，共执行 {executed_count} 个任务")
            
    def should_execute_task(self, task, now):
        """判断是否应该执行某个任务"""
        # 如果从未执行过，检查是否到了执行时间
        if task.last_run is None:
            return self.is_time_to_execute(task, now)
        
        # 如果已经执行过，检查是否到了下一个执行周期
        if task.frequency == 'daily':
            # 每日任务，检查是否是第二天且到了执行时间
            today_execution = datetime(
                now.year, now.month, now.day,
                task.hour, task.minute
            )
            
            # 如果今天还没到执行时间，则不执行
            # 如果今天已经过了执行时间且上次执行不是今天，则执行
            return (now >= today_execution and 
                   task.last_run.date() < now.date())
            
        elif task.frequency == 'weekly':
            # 每周任务，检查是否是下一周且到了执行时间
            # 计算本周目标执行时间
            days_to_target = (task.day_of_week - now.weekday()) % 7
            if days_to_target == 0 and (now.hour > task.hour or (now.hour == task.hour and now.minute >= task.minute)):
                # 本周目标日已过，下次执行是下周
                target_date = now.date() + timedelta(days=(7 - now.weekday() + task.day_of_week))
            elif days_to_target == 0:
                # 本周目标日未到
                target_date = now.date()
            else:
                # 本周目标日
                target_date = now.date() + timedelta(days=days_to_target)
                
            target_execution = datetime(
                target_date.year, target_date.month, target_date.day,
                task.hour, task.minute
            )
            
            # 检查是否到了执行时间且上次执行不在当前执行周期内
            return (now >= target_execution and 
                   (task.last_run.date() < target_date or 
                    (task.last_run.date() == target_date and 
                     (task.last_run.hour < task.hour or 
                      (task.last_run.hour == task.hour and task.last_run.minute < task.minute)))))
            
        elif task.frequency == 'monthly':
            # 每月任务，检查是否是下一月且到了执行时间
            target_execution = self.calculate_next_monthly_run_time(task, now)
            return now >= target_execution and task.last_run < target_execution
        
        return False

    def is_time_to_execute(self, task, now):
        """判断是否到了执行时间（首次执行）"""
        if task.frequency == 'daily':
            # 每日任务，检查当前时间是否匹配
            return now.hour == task.hour and now.minute == task.minute
            
        elif task.frequency == 'weekly':
            # 每周任务，检查星期几和时间是否匹配
            return (now.weekday() == task.day_of_week and 
                    now.hour == task.hour and 
                    now.minute == task.minute)
            
        elif task.frequency == 'monthly':
            # 每月任务，检查日期和时间是否匹配
            return (now.day == task.day_of_month and 
                    now.hour == task.hour and 
                    now.minute == task.minute)
        
        return False

    def calculate_next_monthly_run_time(self, task, now):
        """计算下次月度任务执行时间"""
        try:
            target_execution = datetime(
                now.year, now.month, task.day_of_month,
                task.hour, task.minute
            )
            # 如果本月的指定日期已经过了，则下次执行是下个月
            if target_execution <= now:
                if now.month == 12:
                    target_execution = datetime(
                        now.year + 1, 1, task.day_of_month,
                        task.hour, task.minute
                    )
                else:
                    target_execution = datetime(
                        now.year, now.month + 1, task.day_of_month,
                        task.hour, task.minute
                    )
        except ValueError:
            # 如果指定的日期在该月不存在（如2月30日），则调整到月末
            if now.month == 12:
                if now.year % 4 == 0 and (now.year % 100 != 0 or now.year % 400 == 0):
                    target_execution = datetime(now.year + 1, 1, 29, task.hour, task.minute)
                else:
                    target_execution = datetime(now.year + 1, 1, 28, task.hour, task.minute)
            else:
                next_month = now.month + 1
                year = now.year
                if next_month > 12:
                    next_month = 1
                    year += 1
                    
                # 计算下个月的最后一天
                if next_month in [1, 3, 5, 7, 8, 10, 12]:
                    last_day = 31
                elif next_month in [4, 6, 9, 11]:
                    last_day = 30
                else:  # 2月
                    if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
                        last_day = 29
                    else:
                        last_day = 28
                        
                target_execution = datetime(
                    year, next_month, min(task.day_of_month, last_day),
                    task.hour, task.minute
                )
        
        return target_execution

    def execute_task(self, task, now):
        """执行具体的任务"""
        try:
            if task.frequency == 'daily':
                # 每日报告：发送前一天的消费详情
                end_date = now.date() - timedelta(days=1)
                start_date = end_date
                return send_weekly_report_email(task.email, task.user_id, start_date, end_date)
                
            elif task.frequency == 'weekly':
                # 每周报告：发送上周的消费详情
                # 计算上周一和上周日
                days_since_monday = now.weekday()
                end_date = (now - timedelta(days=days_since_monday + 1)).date()  # 上周日
                start_date = (end_date - timedelta(days=6))  # 上周一
                return send_weekly_report_email(task.email, task.user_id, start_date, end_date)
                
            elif task.frequency == 'monthly':
                # 每月报告：发送上月的消费详情
                # 计算上月的第一天和最后一天
                if now.month == 1:
                    start_date = datetime(now.year - 1, 12, 1).date()
                    if now.year % 4 == 0 and (now.year % 100 != 0 or now.year % 400 == 0):
                        end_date = datetime(now.year - 1, 12, 31).date()
                    else:
                        end_date = datetime(now.year - 1, 12, 31).date()
                else:
                    start_date = datetime(now.year, now.month - 1, 1).date()
                    # 计算上月最后一天
                    prev_month = now.month - 1
                    prev_year = now.year
                    if prev_month in [1, 3, 5, 7, 8, 10, 12]:
                        last_day = 31
                    elif prev_month in [4, 6, 9, 11]:
                        last_day = 30
                    else:  # 2月
                        if prev_year % 4 == 0 and (prev_year % 100 != 0 or prev_year % 400 == 0):
                            last_day = 29
                        else:
                            last_day = 28
                    end_date = datetime(prev_year, prev_month, last_day).date()
                    
                return send_weekly_report_email(task.email, task.user_id, start_date, end_date)
            
            return False
        except Exception as e:
            logger.error(f"执行任务时出错: {str(e)}")
            return False

# 全局调度器实例
scheduler = TaskScheduler()

if __name__ == '__main__':
    # 启动调度器
    scheduler.start()
    
    try:
        # 保持程序运行
        import time
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        # 停止调度器
        scheduler.stop()
        print("调度器已停止")